/**
 *    本规格中描述的特性，扩展或者修改了指针事件中的功能。W3C建议介绍这些事件和相关接口，有助于处理
 * 来自设备的硬件指针输入，包括鼠标、控制笔、触摸屏等。为了兼容现在基于鼠标的指针事件，这篇规格
 * 也为其他指针设备类型介绍了触发鼠标事件的集合。
 */

/**
 *    现在，多数HTML5的内容使用 且/或 的设计来描述鼠标事件。用自定义方式处理那些输入，通常要对鼠标事件编程。
 * 现在的计算设备，往往融入了各种输入设备，比如触摸屏、触屏笔等。这就要求指针事件要兼容这些输入类型。
 * 然而，为这些类型的输入增加支持时，往往会导致成倍的不必要的逻辑和处理代码。专注于一个输入类型又通常
 * 会带来兼容性问题。此外，对于现行的基于鼠标的指针事件进行兼容的方法，大多数 UA 是直接出发鼠标事件。
 * 这就使得事件到底是有真实鼠标触发还是其他输入设备的兼容，这一个问题模糊不清，这就难以同时为两种输入类型事件编程。
 *    为了让兼容多种输入事件更容易，并且能解决上面提到的鼠标设备模糊不清的问题，这篇规格定义了多个输入类型抽象类，
 * 称为“Pointer”。一个 pointer 可以是屏幕上的任意一点，这个点可能由鼠标指针、触屏笔、手指触摸（包括多点触摸）
 * 等输入设备确定。这种模式让网站编程变得更容易，也让应用在多种设备上均可运行良好。想要处理指定类型设备时，
 * 本规范也定义了属性用于识别事件。主要目标是提供一组单一的事件和接口，允许更容易地创作跨设备指针输入，
 * 同时还允许仅在需要增强体验时进行特定于设备的处理。
 *    一个额外重要目标是允许多线程处理默认触摸行为，比如滚动时不会阻塞脚本执行。
 *    处理一般的指针事件看起来与处理鼠标事件很像：pointerdown, pointermove, pointerup, pointerover, pointerout等。
 * 这让开发者很容易可以从鼠标事件迁移的指针事件。指针事件中提供给了所有现在鼠标事件中常用的属性，
 * 也有为其他输入类型提供的新属性：pressure, contact geometry, title等。因此，开发这可以很容易的
 * 在需要时在不同输入类型间共享逻辑，同时也可以为某个特定输入类型提供自定义编程，以求最佳体验。
 *    虽然指针事件来自于多种输入设备，但它没有被定义为生成自特定设备集合的事件。鼓励尽可能考虑兼容性，
 * 本规格不要求一定支持其他特定设备事件。一个 UA 可以支持指针事件而不支持其他任何设备事件。
 * 为了与写入特定于鼠标的事件的内容兼容，本规范提供了一个可选部分，描述了如何基于鼠标以外的设备的指针输入生成兼容鼠标事件。
 */

// 工具函数
const logOwnProperty = (type = '', obj = {}) => {
  console.log(`== ${type}`);
  for(let key in obj) {
    // if(obj.hasOwnProperty(key)) {
      /**
       * 注意：
       *    如果obj是一个 PointerEvent 实例，这里打印的只是 PointerEvent 类中定义的字段，
       * 不包括继承自 MouseEvent 和其他更高级别类的字段。
       */
      console.log(`...${key}: ${obj[key]}`);
    // }
  }
}

// 下面的示例展示了本规范中定义的API如何使用
/**
 * 示例1：特性检查和事件绑定
 */
const featureCheckAndEventBinding = () => {
  let target = document.getElementById('target1');
  if(window.PointerEvent) {
    // 如果 Pointer 事件支持，则监听事件
    target.addEventListener('pointerdown', e => {
      // 必要的话可以根据 e.pointerType（touch/pen/mouse） 分离逻辑
      console.log(e);
      logOwnProperty('PointerEvent', e);
    })
  } else {
    // 传统的 touch/mouse 事件处理
    target.addEventListener('touchstart', e => {
      // 阻止鼠标事件和点击兼容
      e.preventDefault();
      console.log(e);
    });
    target.addEventListener('mousedown', e => {
      console.log(e);
    })
  }
}
// featureCheckAndEventBinding();

/**
 * 示例2：检查用户输入类型
 */
const detectingTypeOfUserInput = () => {
  const detectInputType = (event) => {
    switch (event.pointerType) {
      case 'mouse': console.log('...mouse'); break;
      case 'pen': console.log('...pen'); break;
      case 'touch': console.log('...touch'); break;
      default: break;
    }
  }

  window.addEventListener('pointerdown', detectInputType, false);
};
// detectingTypeOfUserInput();

/**
 * 示例3：基于 HTML5 Canvas 的绘制应用
 */
const basicHTML5CanvasPaintion = () => {
  let canvas = document.getElementById('drawSurface'),
    ctx = canvas.getContext('2d');

  const paint = (event) => {
    if(event.buttons > 0) {
      ctx.fillRect(event.offsetX - 2.5, event.offsetY - 2.5, 5, 5);
    }
  }

  if(window.PointerEvent) {
    canvas.addEventListener('pointermove', paint);
    if(window.navigator.maxTouchPoints > 1) {
      console.log(`== MaxTouchPoints: ${window.navigator.maxTouchPoints}`);
    }
  } else {
    canvas.addEventListener('pointermove', paint);
  }
}
// basicHTML5CanvasPaintion();

/**
 * 示例4：重置一个元素尺寸，以匹配关联的几何样式
 */
const resizingEleToMatchContactGeometry = () => {
  const checkPointerSize = (event) => {
    console.log(event);
    event.target.style.width = event.width + 'px';
    event.target.style.height = event.height + 'px';
  }

  window.addEventListener('pointerdown', checkPointerSize, false);
}
// resizingEleToMatchContactGeometry();

/**
 * 示例5：主动触发非授信的 pointer Event
 */
const firingUntrustedPointerEvent = () => {
  let event = new PointerEvent('pointerover', {
    bubbles: true,
    cancelable: true,
    composed: true,
    pointerId: 42,
    pointerType: 'pen',
    clientX: 300,
    clientY: 500
  });
  window.addEventListener('pointerover', e => {
    console.log(e);
  }, false);
  window.dispatchEvent(event);
}
// firingUntrustedPointerEvent();

/**
 * Constructor(string type[, PointerEventInit eventInitDict]) Exposed=Window
 * PointerEvent 接口字典 : MouseEventInit {
 *    pointerId: {long} 只读，指针事件的唯一标识符。同一时间顶层浏览器上下中所有有效指针中该标识符唯一。如果必要，UA 可以复用已经注销的标识符。
 *    width: {double} 只读，pointer 的接触形状的X轴长度，单位px。因 pointer 事件类型不同而不同。对于缺乏接触形状的输入类型，和没有被硬件捕获到的情况 UA 默认返回1
 *    height: {double} 只读，pointer 的接触形状的Y轴长度，单位px。同上。
 *    pressure: {float} 只读，指针输入的标准化压力值，取值范围[0,1]。0,1分别表示硬件可以检测到的最大最小值。不支持压力感应的设备，有激活的按钮状态时这个值为 0.5否则为0.注意：所有 pointerup 事件的该值为都为0。
 *    tangentialPressure: {float} 只读，同样是标准化的压力值，但取值范围是[-1,1]。通常由附加硬件进行控制，正常状态下该值为 0.注意，有些硬件只支持正值，对于不支持正切压力值的平台和硬件，该值为0.
 *    tiltX: {long} Y-Z面和Y-传感器面相交而成的夹角度数。取值范围为[-90,90]，正值表示偏向右侧。tiltX可以与tiltY一起表示传感器的倾斜状态
 *    tiltY: {long} X-Z面和X-传感器面相交而成的夹角度数。同上
 *    twist: {long} 传感器（通常是触屏笔）顺着主轴顺时针方向旋转的度数，取值范围[0,359]。不支返回扭曲角度的硬件和平台下，该值默认为0
 *    pointerType: {string} 触发当前指针事件的设备类型。对于 UA 无法识别的设备类型，该值为空字符串。未来规格会提供更多的设备类型预设
 *    isPrimary: {boolean} 表明当前指针类型是否是主指针
 * }
 */

/**
 * Button State ———— 多点触碰
 *    有些指针设备，比如鼠标和触屏笔，是支持多点触控的。在鼠标事件模型中，每个触控点按下抬起都会触发
 * mousedown 和 mouseup 事件。为了更好的抽象硬件键的区别更简单的实现跨硬件编写，指针事件在多点触控时
 * 不会触发多个 pointerdown 和 pointerup 事件，而是在最初按下时触发 pointerdown，最后抬起是触发 pointerup
 *    多点触控可以通过 button 和 buttons 属性检测到。button 和 buttons 属性均继承自 MouseEvent 接口，
 * 但在语义和值上有下面这些变化：
 *    button 属性：
 *        为了表示一个指针事件的触控点状态变化，button 属性指示了触发状态变化的事件的设备触控点。取值可能如下：
 *    最后触发事件之前，既没有按钮按下也没有触摸或者触屏笔接触变化：-1
 *    鼠标左键、手指触碰、触屏笔触碰：0
 *    鼠标中键：1
 *    鼠标右键、触屏笔按钮：2
 *    鼠标返回键：3
 *    鼠标前进键：4
 *    触屏笔橡皮按钮：5
 *    
 *    buttons 属性：
 *        这个属性给当前设备按钮状态一个位掩码（与 MouseEvent 中类似，但有一组扩大的可能值），取值如下：
 *    鼠标或触屏笔移动，但没有按下的操作：0
 *    鼠标左键、手指触摸、触屏笔触摸：1
 *    鼠标中键：4，
 *    鼠标右键、触屏笔中键按钮：2
 *    鼠标返回键：8，
 *    鼠标前进键：16
 *    触屏笔橡皮按钮：32
 */

/**
 * 在多点触控的场景下，isPrimary 顺序性用于标识一组激活的触控点中的主控点。
 *    任何时候，一个指针类型中只有一个主控点
 *    同一指针类型中，第一个激活的触控点成为这个类型的主控点
 *    只有主控点会产生兼容的鼠标事件。在有多个主控点的情况下，这些控制点都会产生兼容的鼠标事件。
 */

/**
 * 使用 PointerEvent 接口触发事件
 *    触发一个名为 e 的事件，要按照 DOM4 规范使用 PointerEvent 接口中定义的属性和激活状态。 
 * 如果事件没有 gotpointercapture 或者 lostpointercapture，则运行”进程添加指针捕获“步骤。
 * 按照如下步骤确定事件的 target 对象：
 * 1.如果指针捕获目标被覆盖，将 target 设置为覆盖的对象
 * 2.否则，target 为正常机器触摸返回的对象
 */

// 重点：指针事件类型
/**
 * pointerover 事件：（可冒泡，可取消，无默认行为）
 *    当指针设备移动到碰撞检测元素范围类时触发该事件。注意，setPointerCapture 或者 releasePointerCapture
 * 也许会改变碰撞检测的 target，为了实现触发边界事件的目的，当指针被捕获时，往往认为它处于捕获元素边界框内部。
 * 对于不支持 hover 的设别，UA 还需要在触发 pointerdown 之前触发此事件。
 * 
 * pointerenter 事件：（无冒泡，无取消，无默认行为）
 *    当指针设别移动到碰撞检测元素或者其子孙字节元素的边界框内时，UA 必须触发 pointerenter 事件，
 * 包括作为来自不支持 hover 的设备的 pointerdown 事件的结果。注意，setPointerCapture 或者 releasePointerCapture
 * 可能会改变碰撞检测的 target，并且，为了实现触发边界事件，往往当指针被捕获时被认为一定处于目标元素
 * 边界框内。这个事件类型与 pointerover 相似，不同点在于本事件不支持冒泡。
 * 
 * pointerdown 事件：（冒泡，可取消，如果是主控点，会执行所有 mousedown 事件的默认行为。取消这事件，也会为 pointerTYye 设置“阻止鼠标事件”标识，这会阻止随后可能触发的兼容鼠标的事件。）
 *    当一个指针添加进激活触控点状态时，UA 必须触发一个 pointerdown 指针事件。对鼠标来说，当设别按钮没有按下，
 * 到至少有一个按你按下时触发。对触摸屏，当物理触碰产生数字信号时触发。对触屏笔，即可以在没有按钮按下时，物理触碰产生
 * 数字信号时触发，也可以在没有按钮按下到至少有一个按钮按下时触发。
 *    在触发 pointerdown 事件钱，在不支持 hover 的设备中，应该还要顺序触发 poinerenter 和 pointervoer
 * 
 * pointermove 事件：（冒泡，可取消，会执行与 mousemove 类似的默认行为）
 *    当指针坐标发生改变时，UA 必须书法 pointermove 事件。此外，当一个指针改变按钮状态，压力，正切压力，
 * 倾斜角，扭曲角或者解除图形时，如果没有规范定义对应的事件，则应该处罚 mousemove
 * 
 * pointerup 事件：（冒泡，可取消，会执行与 mouseup 类似的默认行为）
 *    当指针从激活按钮状态列表中删除时触发 pointerup 事件。对鼠标来说，从至少有一个按钮按下，到没有按钮按下时触发。
 * 对触摸屏，当物理接触状态从数字化状态列表中删除时触发。对触屏笔，在没有按钮按下时，物理接触状态从数字化状态列表中删除时触发；
 * 或者从至少有一个按钮按下，到没有按钮按下时触发
 *    在不支持 hover 的设备下，在触发 pointerup 事件后要依次触发 poinerleave 和 pointerout
 * 
 * pointercancel 事件：（冒泡，无取消，无默认行为）
 *    在下面的情况下会触发 pointercancel 事件：
 * · UA 认为一个指针不太可能触发一个事件（比如因为硬件事件）
 * · 已经触发 pointerdown 事件情况下，如果指针随后被用于操作页面视图（比如缩放）
 * · 在拖拽操作开始前，针对拖拽指针做其他操作
 * 在触发 pointercancel 后还应依次触发 pointerleave 和 pointerout
 * 
 * pointerout 事件：（冒泡，可取消，无默认行为）
 *    在下列情形下 UA 必须触发 pointerout 事件：
 * · 指针设备移出元素的碰撞检测边界时触发。
 * · 不支持 hover 的设备中，在 pointerup 事件之后
 * · 在 pointercancel 事件之后
 * · 当触屏笔离开数字一起可检测的范围时
 * 
 * pointerlaeve 事件：（无冒泡，无取消，无默认行为）
 *    当指针设备移出元素（包括所有子元素）碰撞检测区时触发 pointerleave 事件，还包括在不支持 hover 的设备中，
 * pointerout 和 pointercancel 事件之后。
 * 
 * gotpointercapture 事件：（冒泡，无取消，无默认行为）
 *    当元素手动指针捕获时触发 gotpointercapture 事件。紧接着这个指针上的所有事件都触发在哪个元素上。
 * 
 * lostpointercapture 事件：（冒泡，无取消，无默认行为）
 *    当一个指针释放指针捕获时触发 lostpointercapture 事件。这个事件在捕获释放后必须触发在同元素的其他事件之前。
 * 这个事件触发在删除指针捕获的元素上。紧接着的事件正常地通过碰撞检测确定事件目标对象。
 */

/**
 * 本规范扩展了 Element 接口 {
 *    void setPointerCapture (long pointerId): 
 *        给调用本方法的元素通过参数 pointerId 设置指针捕获值。捕获目标将替换正常的命中测试结果，就像指针始终位于捕获目标之上一样，
 * 并且在释放捕获之前，它们必须始终针对此元素。这个指针的“激活按钮状态”必须处于激活状态，否色会静默。
 * 如果入参没有匹配到激活的指针则抛出 NotFoundError 异常。
 *    
 *    void releasePointerCapture (long pointerId)：
 *        从调用该方法的元素上释放指定 pointerId 的指针的捕获对象。紧接着的事件捕获对象有碰撞检测确定，
 * 如果给出的 pointerId 没有匹配到激活的指针，则抛出 NotFoundError 异常。
 * 
 *    boolean hasPoinerCapture (long pointerId) ：
 *        检查当前调用方法的元素上是否有指定 poinetrId 的指针被捕获。调用元素上，如果“添加指针捕获目标覆盖”标识
 * 被设置，则返回 true，否则 false 
 * }
 */

/**
 * 本规范扩展了 Navigator 接口 {
 *    readonly attribute long maxTouchPoints:
 *        当前设备支持同时触摸的最大触控点数。在有多个转换器的情况下，这个值是各个转换器支持最大值组
 * 成的集合中的最大值
 * }
 */

/**
 * 对于触摸输入，所有指针事件的默认行为一定不能操作视图
 * touch-active css 属性
 *    值：auto | none | [pax-x || pan-y] | mainpulation
 *    默认：auto
 *    适用于：除了非替换行间元素、表行、行组、表列和列组 外的所有元素
 *  决定触摸输入是否触发 UA 默认行为，这包括但不限于平移或者缩放。示例如下
 *  <div style="touch-action: none;"></div> 禁用所有触摸行为
 *  <div style="touch-action: pan-x"></div> 只允许水平平移
 *  <div style="touch-action: auto"> 子级区域禁用触摸行为
 *    <div style="touch-action: none"></div>
 *    <div></div>
 *  </div>
 *  <div style="overflow: auto;"> 中间级禁用触摸行为
 *    <div style="touch-action: pan-y">
 *      <div style="touch-action: pan-x"></div>
 *    </div>
 *  </div>
 */